package lion.framework.core.task.rule;

import java.util.Calendar;
import java.util.Date;

import org.apache.commons.lang.StringUtils;

/**
 * Cron表达式
 * 
 * @author lion
 * @email hl_0074@sina.com
 * @date 2016年3月18日
 */
public class Cron {

	private CronPos[] poses;

	public Cron(String cron) {

		// 不能有两个?号
		if (cron.indexOf("?") != cron.lastIndexOf("?")) { throw new IllegalArgumentException("illegalargument cron expression " + cron); }

		parse(cron);
	}

	private void parse(String cron) {

		String[] arr = StringUtils.split(cron, " ");
		if (arr.length != 6 && arr.length != 7) { return; }
		poses = new CronPos[7];
		for (int i = 0; i < arr.length; i++) {
			poses[i] = new CronPos(arr[i]);
		}
		if (poses[6] == null) {
			poses[6] = new CronPos("*");
		}

		// chagne s min h d mon w y to s min h mon y d w
		CronPos tmp = poses[3];// date

		poses[3] = poses[4];// month
		poses[4] = poses[6];// year
		poses[6] = poses[5];// week
		poses[5] = tmp;// date
	}

	public Date getStartTime() {

		int i = 0;
		Calendar c = Calendar.getInstance();
		setField(poses[i++], Calendar.SECOND, c, 0, 60);
		setField(poses[i++], Calendar.MINUTE, c, 0, 60);
		setField(poses[i++], Calendar.HOUR_OF_DAY, c, 0, 23);
		setField(poses[i++], Calendar.MONTH, c, 0, 11);
		setField(poses[i++], Calendar.YEAR, c, -1, -1);

		// 特殊处理
		setDate(poses[i++], c, false);
		setDayOfWeek(poses[i], c, false);
		return c.getTime();
	}

	public Date getEndTime() {

		int i = 0;
		Calendar c = Calendar.getInstance();
		setMaxField(poses[i++], Calendar.SECOND, c, 0, 59);
		setMaxField(poses[i++], Calendar.MINUTE, c, 0, 59);
		setMaxField(poses[i++], Calendar.HOUR_OF_DAY, c, 0, 23);

		setMaxField(poses[i++], Calendar.MONTH, c, 0, 11);

		setMaxField(poses[i++], Calendar.YEAR, c, -1, -1);
		setDate(poses[i++], c, true);

		// 以下两个特殊处理
		setDayOfWeek(poses[i], c, true);

		return c.getTime();
	}

	public boolean matches(Date date) {

		int i = 0;
		Calendar c = Calendar.getInstance();
		c.setTime(date);
		if (!poses[i++].match(c.get(Calendar.SECOND))) { return false; }
		if (!poses[i++].match(c.get(Calendar.MINUTE))) { return false; }
		if (!poses[i++].match(c.get(Calendar.HOUR_OF_DAY))) { return false; }
		if (!poses[i++].match(c.get(Calendar.MONTH))) { return false; }
		if (!poses[i++].match(c.get(Calendar.YEAR))) { return false; }
		if (!poses[i++].match(c.get(Calendar.DATE))) { return false; }
		if (!poses[i++].match(c.get(Calendar.DAY_OF_WEEK))) { return false; }

		return true;
	}

	private void setMaxField(CronPos pos, int field, Calendar c, int min, int max) {

		if (pos.getType() == CronPos.TYPE_NONSENS) { return; }

		int val = -1;
		switch (pos.getType()) {
		case CronPos.TYPE_EVERY:
			if (field != Calendar.YEAR) {
				val = c.getActualMaximum(field);
			}
			break;
		case CronPos.TYPE_LIST:
			val = pos.getVals()[pos.getVals().length - 1];
			break;
		case CronPos.TYPE_RANGE:
			val = pos.getEnd();
			break;
		case CronPos.TYPE_INTERVAL:
			int end = c.getMaximum(field);
			val = end - (end - pos.getStart()) % pos.getInterval();
			break;
		case CronPos.TYPE_RANGE_INTERVAL:
			val = pos.getEnd() - (pos.getEnd() - pos.getStart()) % pos.getInterval();
			break;
		case CronPos.TYPE_VALUE:
			val = pos.getValue();
		case CronPos.TYPE_NONSENS:
			break;
		}
		if (val < 0) { return; }
		if ((val < min || val > max) && (min != max)) { return; }
		c.set(field, val);
	}

	private void setField(CronPos pos, int field, Calendar c, int min, int max) {

		if (pos.getType() == CronPos.TYPE_EVERY && pos.getType() == CronPos.TYPE_NONSENS) { return; }
		int val = -1;
		switch (pos.getType()) {
		case CronPos.TYPE_INTERVAL:
		case CronPos.TYPE_RANGE:
		case CronPos.TYPE_RANGE_INTERVAL:
			val = pos.getStart();
			break;
		case CronPos.TYPE_LIST:
			val = pos.getVals()[0];
			break;
		case CronPos.TYPE_VALUE:
			val = pos.getValue();
		}
		if (val < 0) { return; }
		if ((val < min || val > max) && (min != max)) { return; }
		c.set(field, val);
	}

	private void setDayOfWeek(CronPos pos, Calendar c, boolean max) {

		if (pos.getType() == CronPos.TYPE_L) {
			c.set(Calendar.DAY_OF_WEEK_IN_MONTH, c.getActualMaximum(Calendar.DAY_OF_WEEK_IN_MONTH));
			c.set(Calendar.DAY_OF_WEEK, pos.getValue());
		} else if (pos.getType() == CronPos.TYPE_SHARP) {
			c.set(Calendar.DAY_OF_WEEK_IN_MONTH, pos.getWom());
			c.set(Calendar.DAY_OF_WEEK, pos.getDow());
		} else {
			if (max) {
				setMaxField(pos, Calendar.DAY_OF_WEEK, c, 1, 7);
			} else {
				setField(pos, Calendar.DAY_OF_WEEK, c, 1, 7);
			}
		}
	}

	private void setDate(CronPos pos, Calendar c, boolean max) {

		if (pos.getType() == CronPos.TYPE_L) {
			c.set(Calendar.DATE, c.getActualMaximum(Calendar.DATE));
		} else if (pos.getType() == CronPos.TYPE_LW) {
			c.set(Calendar.DAY_OF_WEEK_IN_MONTH, c.getActualMaximum(Calendar.DAY_OF_WEEK_IN_MONTH));
			c.set(Calendar.DAY_OF_WEEK, Calendar.FRIDAY);
		} else if (pos.getType() == CronPos.TYPE_W) {
			c.set(Calendar.DATE, pos.getValue());
			int w = c.get(Calendar.DAY_OF_WEEK);
			if (w == 1) {// 周日
				int date = c.get(Calendar.DATE);
				c.add(Calendar.DATE, 1);
				if (c.get(Calendar.DATE) < date) {
					c.add(Calendar.DATE, -3);
				}
			} else if (w == 7) {// 周六
				int date = c.get(Calendar.DATE);
				c.add(Calendar.DATE, -1);
				if (c.get(Calendar.DATE) < date) {
					c.add(Calendar.DATE, 3);
				}
			}
		} else {
			if (max) {
				setMaxField(pos, Calendar.DATE, c, 0, 31);
			} else {
				setField(pos, Calendar.DATE, c, 0, 31);
			}
		}
	}

}
